// Public use
export { getGlobalContext }
export { getGlobalContextSync }
export { getGlobalContextAsync }

// Internal use
export { getGlobalContextServerInternal }
export { getGlobalContextServerInternalOptional }
export { getViteDevServer }
export { getViteConfig }
export { initGlobalContext_renderPage }
export { initGlobalContext_runPrerender }
export { initGlobalContext_getPagesAndRoutes }
export { setGlobalContext_viteDevServer }
export { setGlobalContext_viteConfig }
export { setGlobalContext_isPrerendering }
export { setGlobalContext_prerenderContext }
export { setGlobalContext_isProductionAccordingToVite }
export { setGlobalContext_prodBuildEntry } // production entry
export { clearGlobalContext }
export { assertBuildInfo }
export { updateUserFiles }
export { isRunnable }
export { vikeConfigErrorRecoverMsg }
export type { BuildInfo }
export type { GlobalContextServerInternal }

// The core logic revolves around:
// - virtualFileExportsGlobalEntry is the main requirement
// - In production: globalObject.prodBuildEntry which is the production entry set by @brillout/vite-plugin-server-entry
//   - loadProdBuildEntry() sets globalObject.prodBuildEntry and then sets virtualFileExportsGlobalEntry
//   - With vike-server it's set at server start: @brillout/vite-plugin-server-entry injects `import './entry.mjs'` (the production entry generated by @brillout/vite-plugin-server-entry) as first line of code of dist/server/index.mjs while dist/server/entry.mjs calls setGlobalContext_prodBuildEntry()
//   - Without vike-server it's manually loaded here using importServerProductionEntry() which uses @brillout/vite-plugin-server-entry's autoImporter or crawler
// - In development: globalObject.viteDevServer which is Vite's development server
//   - globalObject.viteDevServer is used by updateUserFiles() which then sets virtualFileExportsGlobalEntry

import {
  assert,
  onSetupRuntime,
  assertUsage,
  assertWarning,
  isPlainObject,
  objectReplace,
  isObject,
  hasProp,
  getGlobalObject,
  genPromise,
  createDebug,
  checkType,
  PROJECT_VERSION,
  getViteRPC,
  isRunnableDevEnvironment,
  assertIsNotBrowser,
  isNonRunnableDev,
  objectAssign,
  setNodeEnvProductionIfUndefined,
} from '../utils.js'
import type { ViteManifest } from '../../types/ViteManifest.js'
import type { ResolvedConfig, ViteDevServer } from 'vite'
import { importServerProductionEntry } from '@brillout/vite-plugin-server-entry/runtime'
import { virtualFileIdGlobalEntryServer } from '../../shared-server-node/virtualFileId.js'
import pc from '@brillout/picocolors'
import type { GlobalConfigPublic } from '../../shared-server-client/page-configs/resolveVikeConfigPublic.js'
import { loadPageRoutes, loadPageRoutesSync, type PageRoutes } from '../../shared-server-client/route/loadPageRoutes.js'
import { assertV1Design } from '../../shared-server-node/assertV1Design.js'
import { resolveBase } from '../../shared-server-node/resolveBase.js'
import type { ViteConfigRuntime } from '../../node/vite/shared/getViteConfigRuntime.js'
import {
  createGlobalContextShared,
  getGlobalContextSyncErrMsg,
  type GlobalContextBase,
} from '../../shared-server-client/createGlobalContextShared.js'
import type { GlobalContext, GlobalContextServer } from '../../types/GlobalContext.js'
import { prepareGlobalContextForPublicUsage } from '../../shared-server-client/prepareGlobalContextForPublicUsage.js'
import { logRuntimeError, logRuntimeInfo } from './loggerRuntime.js'
import { getVikeConfigErrorBuild, setVikeConfigError } from '../../shared-server-node/getVikeConfigError.js'
import type { Hook } from '../../shared-server-client/hooks/getHook.js'
import type { ViteRPC } from '../../node/vite/plugins/non-runnable-dev/pluginViteRPC.js'
import { getVikeApiOperation } from '../../shared-server-node/api-context.js'
import type { PrerenderContext } from '../../types/index.js'
import { hasAlreadyLogged } from './logErrorServer.js'
const debug = createDebug('vike:globalContext')
const globalObject = getGlobalObject<
  {
    globalContext?: Record<string, unknown>
    viteDevServer?: ViteDevServer
    viteConfig?: ResolvedConfig
    viteConfigRuntime?: ViteConfigRuntime
    isPrerendering?: true
    prerenderContextPublic?: PrerenderContext
    initGlobalContext_runPrerender_alreadyCalled?: true
    prodBuildEntry?: unknown
    prodBuildEntryPrevious?: unknown
    waitForUserFilesUpdate?: Promise<void>
    waitForUserFilesUpdateResolve?: (() => void)[]
    vikeConfigHasRuntimeError?: boolean
    buildInfo?: BuildInfo
    // Move to buildInfo.assetsManifest ?
    assetsManifest?: ViteManifest
    isInitialized?: true
    isAfterFirstRenderPageCall?: true
    isProductionAccordingToUser?: boolean
    isProductionAccordingToPhotonVercel?: true
    isProductionAccordingToVite?: boolean
  } & ReturnType<typeof getInitialGlobalObject>
>('runtime/globalContext.ts', getInitialGlobalObject())
// Trick to break down TypeScript circular dependency
// https://chat.deepseek.com/a/chat/s/d7e9f90a-c7f3-4108-9cd5-4ad6caed3539
const globalObjectTyped = globalObject as typeof globalObject & {
  globalContext?: GlobalContextServerInternal
}
const vikeConfigErrorRecoverMsg = pc.bold(pc.green('Vike config loaded'))
assertIsNotBrowser()

// Internal usage
type GlobalContextServerInternal = Awaited<ReturnType<typeof createGlobalContext>>

async function getGlobalContextServerInternal() {
  // getGlobalContextServerInternal() should always be called after initGlobalContext()
  assert(globalObject.isInitialized)
  assertGlobalContextIsDefined()
  if (!isProd()) await globalObject.waitForUserFilesUpdate
  const { globalContext } = globalObjectTyped
  assertIsDefined(globalContext)
  return { globalContext }
}

function getGlobalContextServerInternalOptional() {
  const { globalContext } = globalObjectTyped
  if (!globalContext) return null
  return globalContext
}

function assertIsDefined<T extends GlobalContextServerInternal>(
  globalContext: undefined | null | T,
): asserts globalContext is T {
  if (!globalContext) {
    debug('globalContext', globalContext)
    debug('assertIsDefined()', new Error().stack)
    assert(false)
  }
}
function assertGlobalContextIsDefined() {
  assertIsDefined(globalObjectTyped.globalContext)
  assert(globalObject.globalContext)
}

// We purposely return GlobalContext instead of GlobalContextServer because `import { getGlobalContext } from 'vike'` can resolve to the client-side implementation.
/**
 * Get runtime information about your app.
 *
 * https://vike.dev/getGlobalContext
 */
async function getGlobalContext(): Promise<GlobalContext> {
  debug('getGlobalContext()')
  const isProduction = isProdOptional()
  // This assertion cannot fail for vike-server users (because when using vike-server it's guaranteed that globalObject.isProduction is set before executing any user-land code and any Vike extension code).
  assertUsage(isProduction !== null, "The global context isn't set yet, use getGlobalContextAsync() instead.")
  return await getGlobalContextAsync(isProduction)
}
/**
 * Get runtime information about your app.
 *
 * https://vike.dev/getGlobalContext
 */
async function getGlobalContextAsync(isProduction: boolean): Promise<GlobalContext> {
  debug('getGlobalContextAsync()')
  assertUsage(
    typeof isProduction === 'boolean',
    `[getGlobalContextAsync(isProduction)] Argument ${pc.cyan('isProduction')} ${
      isProduction === undefined ? 'is missing' : `should be ${pc.cyan('true')} or ${pc.cyan('false')}`
    }`,
  )
  globalObject.isProductionAccordingToUser = isProduction
  if (!globalObject.globalContext) await initGlobalContext_getGlobalContextAsync()
  if (!isProduction) await globalObject.waitForUserFilesUpdate
  assertGlobalContextIsDefined()
  return getGlobalContextForPublicUsage()
}
/**
 * Get runtime information about your app.
 *
 * https://vike.dev/getGlobalContext
 */
function getGlobalContextSync(): GlobalContext {
  debug('getGlobalContextSync()')
  const { globalContext } = globalObjectTyped
  assertUsage(globalContext, getGlobalContextSyncErrMsg)
  assertWarning(
    isProd(),
    // - We discourage users from using it in development because `pageContext.globalContext` is safer: I ain't sure but there could be race conditions when using `getGlobalContextSync()` inside React/Vue components upon HMR.
    // - I don't see any issues with getGlobalContextSync() in production.
    // - getGlobalContextSync() is used in production by vike-vercel
    //   - https://discord.com/channels/@me/942519153502339072/1389546794676916344 (PM between Rom and Joël)
    "getGlobalContextSync() shouldn't be used in development, see https://vike.dev/getGlobalContext",
    { onlyOnce: true },
  )
  return getGlobalContextForPublicUsage()
}
function getGlobalContextForPublicUsage(): GlobalContextServer {
  const { globalContext } = globalObjectTyped
  assert(globalContext)
  const globalContextForPublicUsage = prepareGlobalContextForPublicUsage(globalContext)
  return globalContextForPublicUsage
}

async function setGlobalContext_viteDevServer(viteDevServer: ViteDevServer) {
  debug('setGlobalContext_viteDevServer()')
  // We cannot cache globalObject.viteDevServer because it's fully replaced when the user modifies vite.config.js => Vite's dev server is fully reloaded and a new viteDevServer replaces the previous one.
  if (!globalObject.viteDevServer) {
    assertIsNotInitializedYet()
  }
  assert(globalObject.viteConfig)
  globalObject.viteDevServer = viteDevServer
  globalObject.viteDevServerPromiseResolve(viteDevServer)

  if (isRunnable(viteDevServer)) {
    const { success } = await updateUserFiles()
    if (!success) return
    assertGlobalContextIsDefined()
  }
}
function setGlobalContext_viteConfig(viteConfig: ResolvedConfig, viteConfigRuntime: ViteConfigRuntime): void {
  if (globalObject.viteConfig) return
  assertIsNotInitializedYet()
  globalObject.viteConfig = viteConfig
  globalObject.viteConfigRuntime = viteConfigRuntime
}
function assertIsNotInitializedYet() {
  // In development, globalObject.viteDevServer always needs to be awaited for before initializing globalObject.globalContext
  assert(!globalObject.globalContext)
}
function setGlobalContext_isPrerendering() {
  globalObject.isPrerendering = true
}
function setGlobalContext_prerenderContext(prerenderContextPublic: PrerenderContext) {
  globalObject.prerenderContextPublic = prerenderContextPublic

  // Ugly redundancy, which we can remove after globalContext is a proxy
  const { globalContext } = globalObjectTyped
  if (globalContext) globalContext.prerenderContext = prerenderContextPublic
}
function setGlobalContext_isProductionAccordingToVite(isProductionAccordingToVite: boolean) {
  globalObject.isProductionAccordingToVite = isProductionAccordingToVite
}

function getViteDevServer(): ViteDevServer | null {
  return globalObject.viteDevServer ?? null
}
function getViteConfig(): ResolvedConfig | null {
  return globalObject.viteConfig ?? null
}

async function initGlobalContext_renderPage(): Promise<void> {
  debug('initGlobalContext_renderPage()')
  globalObject.isAfterFirstRenderPageCall = true
  await initGlobalContext()
}

async function initGlobalContext_runPrerender(): Promise<void> {
  debug('initGlobalContext_runPrerender()')
  assert(globalObject.isPrerendering === true)
  assert(isProd())
  if (globalObject.initGlobalContext_runPrerender_alreadyCalled) return
  globalObject.initGlobalContext_runPrerender_alreadyCalled = true

  assert(globalObject.isPrerendering)
  assert(globalObject.viteConfig)

  // We assume initGlobalContext_runPrerender() to be called before:
  // - initGlobalContext_renderPage()
  // - initGlobalContext_getGlobalContextAsync()
  assertIsNotInitializedYet()

  await initGlobalContext()
}

async function initGlobalContext_getGlobalContextAsync(): Promise<void> {
  debug('initGlobalContext_getGlobalContextAsync()')
  await initGlobalContext()
}
async function initGlobalContext_getPagesAndRoutes(): Promise<void> {
  debug('initGlobalContext_getPagesAndRoutes()')
  globalObject.isProductionAccordingToPhotonVercel = true
  await initGlobalContext()
}
async function initGlobalContext(): Promise<void> {
  const isProduction = isProd()
  if (!isProduction) {
    if (isProcessSharedWithVite()) {
      await globalObject.viteDevServerPromise
    } else {
      assert(isNonRunnableDev())
      await updateUserFiles()
    }
    assert(globalObject.waitForUserFilesUpdate)
    await globalObject.waitForUserFilesUpdate
  } else {
    await loadProdBuildEntry(globalObject.viteConfigRuntime?.build.outDir)
  }
  assertGlobalContextIsDefined()
  globalObject.isInitialized = true
}

function assertViteManifest(manifest: unknown): asserts manifest is ViteManifest {
  assert(isPlainObject(manifest))
  /* We should include these assertions but we don't as a workaround for PWA manifests: https://github.com/vikejs/vike/issues/769
     Instead, we should rename the vite manifest e.g. with https://vitejs.dev/config/build-options.html#build-manifest
  Object.entries(manifest)
    // circumvent esbuild bug: esbuild adds a `default` key to JSON upon `require('./some.json')`.
    .filter(([key]) => key !== 'default')
    .forEach(([_, entry]) => {
      assert(isPlainObject(entry))
      assert(typeof entry.file === 'string')
    })
  */
}

async function loadProdBuildEntry(outDir?: string) {
  debug('loadProdBuildEntry()')
  if (globalObject.globalContext) {
    debug('loadProdBuildEntry() - already done')
    return
  }
  if (!globalObject.prodBuildEntry) {
    debug('importServerProductionEntry()')
    // importServerProductionEntry() loads dist/server/entry.mjs which calls setGlobalContext_prodBuildEntry()
    await importServerProductionEntry({ outDir })
    if (!globalObject.prodBuildEntry) {
      debug('globalObject.prodBuildEntryPrevious')
      // Needed, for example, when calling the API prerender() then preview() because both trigger a importServerProductionEntry() call but only the first only is applied because of the import() cache. (A proper implementation would be to clear the import() cache, but it probably isn't possible on platforms such as Cloudflare Workers.)
      globalObject.prodBuildEntry = globalObject.prodBuildEntryPrevious
    }
    assert(globalObject.prodBuildEntry)
    // If using `inject` then dist/server/index.js imports dist/server/entry.js and loadProdBuildEntry() isn't needed.
    // If dist/server/entry.js isn't imported then this means the user is running the original server entry `$ ts-node server/index.ts`.
    assertWarning(
      // vike-server => `inject === true`
      // vike-node => `inject === [ 'index' ]` => we don't show the warning to vike-node users (I don't remember why).
      globalObject.buildInfo?.viteConfigRuntime.vitePluginServerEntry.inject !== true || globalObject.isPrerendering,
      `Run the built server entry (e.g. ${pc.cyan('$ node dist/server/index.mjs')}) instead of the original server entry (e.g. ${pc.cyan('$ ts-node server/index.ts')})`,
      { onlyOnce: true },
    )
  }
  const { prodBuildEntry } = globalObject
  assertProdBuildEntry(prodBuildEntry)
  globalObject.assetsManifest = prodBuildEntry.assetsManifest
  globalObject.buildInfo = prodBuildEntry.buildInfo
  await createGlobalContext(prodBuildEntry.virtualFileExportsGlobalEntry)
}

// This is the production entry, see:
// https://github.com/vikejs/vike/blob/8c350e8105a626469e87594d983090919e82099b/packages/vike/node/vite/plugins/pluginBuild/pluginProdBuildEntry.ts#L47
async function setGlobalContext_prodBuildEntry(prodBuildEntry: unknown) {
  debug('setGlobalContext_prodBuildEntry()')
  assert(!isNonRunnableDev())
  assertProdBuildEntry(prodBuildEntry)
  setNodeEnvProductionIfUndefined()
  globalObject.prodBuildEntry = prodBuildEntry
  globalObject.prodBuildEntryPrevious = prodBuildEntry
  assert(globalObject.prodBuildEntry) // ensure no infinite loop
  await loadProdBuildEntry()
  assertGlobalContextIsDefined()
  debug('setGlobalContext_prodBuildEntry() - done')
}

type ProdBuildEntry = {
  virtualFileExportsGlobalEntry: Record<string, unknown>
  assetsManifest: ViteManifest
  buildInfo: BuildInfo
}
type BuildInfo = {
  versionAtBuildTime: string
  usesClientRouter: boolean // TO-DO/next-major-release: remove
  viteConfigRuntime: ViteConfigRuntime
}
function assertProdBuildEntry(prodBuildEntry: unknown): asserts prodBuildEntry is ProdBuildEntry {
  assert(isObject(prodBuildEntry))
  assert(hasProp(prodBuildEntry, 'virtualFileExportsGlobalEntry', 'object'))
  const { virtualFileExportsGlobalEntry } = prodBuildEntry
  assert(hasProp(prodBuildEntry, 'assetsManifest', 'object'))
  const { assetsManifest } = prodBuildEntry
  assertViteManifest(assetsManifest)
  assert(hasProp(prodBuildEntry, 'buildInfo', 'object'))
  const { buildInfo } = prodBuildEntry
  assertBuildInfo(buildInfo)
  checkType<ProdBuildEntry>({ virtualFileExportsGlobalEntry, assetsManifest, buildInfo })
}
function assertBuildInfo(buildInfo: unknown): asserts buildInfo is BuildInfo {
  assert(isObject(buildInfo))
  assert(hasProp(buildInfo, 'versionAtBuildTime', 'string'))
  assertVersionAtBuildTime(buildInfo.versionAtBuildTime)
  assert(hasProp(buildInfo, 'viteConfigRuntime', 'object'))
  assert(hasProp(buildInfo.viteConfigRuntime, '_baseViteOriginal', 'string'))
  assert(hasProp(buildInfo.viteConfigRuntime, 'root', 'string'))
  assert(hasProp(buildInfo.viteConfigRuntime, 'build', 'object'))
  assert(hasProp(buildInfo.viteConfigRuntime.build, 'outDir', 'string'))
  assert(hasProp(buildInfo.viteConfigRuntime, 'vitePluginServerEntry', 'object'))
  assert(hasProp(buildInfo, 'usesClientRouter', 'boolean'))
}
function assertVersionAtBuildTime(versionAtBuildTime: string) {
  const versionAtRuntime = PROJECT_VERSION
  const pretty = (version: string) => pc.bold(`vike@${version}`)
  assertUsage(
    versionAtBuildTime === versionAtRuntime,
    `Re-build your app (you're using ${pretty(versionAtRuntime)} but your app was built with ${pretty(versionAtBuildTime)})`,
  )
}

// TODO: recover from +onCreateGlobalContext runtime error doesn't work
async function updateUserFiles(): Promise<{ success: boolean }> {
  debug('updateUserFiles()')
  assert(!isProd())
  const { promise, resolve } = genPromise<void>()
  globalObject.waitForUserFilesUpdate = promise
  globalObject.waitForUserFilesUpdateResolve ??= []
  globalObject.waitForUserFilesUpdateResolve.push(resolve)

  const onError = (err: unknown) => {
    if (
      // We must check whether the error was already logged to avoid printing it twice, e.g. when +onCreateGlobalContext.js has a syntax error
      !hasAlreadyLogged(err)
    ) {
      logRuntimeError(err, null)
    }
    setVikeConfigError({ errorRuntime: { err } })
    globalObject.vikeConfigHasRuntimeError = true
    return { success: false }
  }
  const onSuccess = () => {
    if (globalObject.vikeConfigHasRuntimeError) {
      assert(logRuntimeInfo) // always defined in dev
      logRuntimeInfo(vikeConfigErrorRecoverMsg, null, 'error-resolve')
    }
    globalObject.vikeConfigHasRuntimeError = false
    setVikeConfigError({ errorRuntime: false })
    globalObject.waitForUserFilesUpdateResolve!.forEach((resolve) => resolve())
    globalObject.waitForUserFilesUpdateResolve = []
    resolve()
    return { success: true }
  }

  const isOutdated = () =>
    // There is a newer call — let the new call supersede the old one.
    // We deliberately swallow the intermetidate state (including any potential error) — it's now outdated and has existed only for a very short period of time.
    globalObject.waitForUserFilesUpdate !== promise ||
    // Avoid race condition: abort if there is a new globalObject.viteDevServer (happens when vite.config.js is modified => Vite's dev server is fully reloaded).
    viteDevServer !== globalObject.viteDevServer

  const { viteDevServer } = globalObject
  let hasError = false
  let virtualFileExportsGlobalEntry: Record<string, unknown> | undefined
  let err: unknown
  if (viteDevServer) {
    assert(isRunnable(viteDevServer))

    /* We don't use runner.import() yet, because as of vite@7.0.6 (July 2025) runner.import() unexpectedly invalidates the module graph, which is a unexpected behavior that doesn't happen with ssrLoadModule()
    // Vite 6
    try {
      virtualFileExportsGlobalEntry = await (viteDevServer.environments.ssr as RunnableDevEnvironment).runner.import(
        'virtual:vike:global-entry:server',
      )
    } catch (err_) {
      hasError = true
      err = err_
    }
    */

    // Vite 5
    try {
      virtualFileExportsGlobalEntry = await viteDevServer.ssrLoadModule(virtualFileIdGlobalEntryServer)
    } catch (err_) {
      hasError = true
      err = err_
    }
  } else {
    try {
      // We don't directly use import() because:
      // - Avoid Cloudflare Workers (without @cloudflare/vite-plugin) to try to bundle `import('virtual:id')`.
      // - Using import() seems to lead to a Vite HMR bug:
      //   ```js
      //   assert(false)
      //   // This line breaks the HMR of regular (runnable) apps, even though (as per the assert() above) it's never run. It seems to be a Vite bug: handleHotUpdate() receives an empty `modules` list.
      //   import('virtual:vike:global-entry:server')
      //   ```
      virtualFileExportsGlobalEntry = await __VIKE__DYNAMIC_IMPORT('virtual:vike:global-entry:server')
    } catch (err_) {
      hasError = true
      err = err_
    }
  }
  if (isOutdated()) return { success: false }
  if (hasError) return onError(err)
  virtualFileExportsGlobalEntry = (virtualFileExportsGlobalEntry as any).default || virtualFileExportsGlobalEntry

  if (getVikeConfigErrorBuild()) {
    return { success: false }
  }

  try {
    await createGlobalContext(virtualFileExportsGlobalEntry)
  } catch (err_) {
    hasError = true
    err = err_
  }
  if (isOutdated()) return { success: false }
  if (hasError) return onError(err)
  return onSuccess()
}

async function createGlobalContext(virtualFileExportsGlobalEntry: unknown) {
  debug('createGlobalContext()')
  assert(!getVikeConfigErrorBuild())

  const globalContextPromise = createGlobalContextShared(
    virtualFileExportsGlobalEntry,
    globalObject,
    addGlobalContext,
    addGlobalContextTmp,
    addGlobalContextAsync,
  )

  debug('createGlobalContext() - done [sync]')
  // We define an early globalContext version synchronously, so that getGlobalContextSync() can be called early.
  // - Required by vike-vercel
  assert(globalObject.globalContext)

  const globalContext = await globalContextPromise
  debug('createGlobalContext() - done [async]')

  assertV1Design(
    // pageConfigs is PageConfigRuntime[] but assertV1Design() requires PageConfigBuildTime[]
    globalContext._pageConfigs.length > 0,
    globalContext._pageFilesAll,
  )
  assertGlobalContextIsDefined()
  onSetupRuntime()

  objectAssign(globalContext, { prerenderContext: globalObject.prerenderContextPublic })

  // Never actually used, only used for TypeScript `ReturnType<typeof createGlobalContext>`
  return globalContext
}

async function addGlobalContextTmp(globalContext: GlobalContextBase) {
  debug('addGlobalContextTmp()')
  const { pageRoutes, onBeforeRouteHook } = await loadPageRoutes(
    globalContext._pageFilesAll,
    globalContext._pageConfigs,
    globalContext._pageConfigGlobal,
    globalContext._allPageIds,
  )
  return addGlobalContextCommon(globalContext, pageRoutes, onBeforeRouteHook)
}
function addGlobalContext(globalContext: GlobalContextBase) {
  debug('addGlobalContext()')
  const { pageRoutes, onBeforeRouteHook } = loadPageRoutesSync(
    globalContext._pageFilesAll,
    globalContext._pageConfigs,
    globalContext._pageConfigGlobal,
    globalContext._allPageIds,
  )
  return addGlobalContextCommon(globalContext, pageRoutes, onBeforeRouteHook)
}
function addGlobalContextCommon(
  globalContext: GlobalContextBase,
  pageRoutes: PageRoutes,
  onBeforeRouteHook: null | Hook,
) {
  const globalContextBase = {
    isClientSide: false as const,
    _pageRoutes: pageRoutes,
    _onBeforeRouteHook: onBeforeRouteHook,
  }
  const { viteDevServer, viteConfig, isPrerendering } = globalObject
  const isProduction = isProd()
  if (!isProduction) {
    assert(globalContext) // main common requirement
    assert(!isPrerendering)
    return {
      ...globalContextBase,
      _isProduction: false as const,
      _isPrerendering: false as const,
      assetsManifest: null,
      _viteDevServer: viteDevServer,
      viteConfig,
    }
  } else {
    assert(globalObject.prodBuildEntry)
    assert(globalContext) // main common requiement
    const { buildInfo, assetsManifest } = globalObject
    assert(buildInfo)
    assert(assetsManifest)
    const globalContextBase2 = {
      ...globalContextBase,
      _isProduction: true as const,
      assetsManifest,
      _viteDevServer: null,
      _usesClientRouter: buildInfo.usesClientRouter,
    }
    if (isPrerendering) {
      assert(viteConfig)
      return {
        ...globalContextBase2,
        _isPrerendering: true as const,
        viteConfig,
      }
    } else {
      return {
        ...globalContextBase2,
        _isPrerendering: false as const,
        viteConfig: null,
      }
    }
  }
}
async function addGlobalContextAsync(globalContext: GlobalContextBase) {
  debug('addGlobalContextAsync()')
  let { viteConfigRuntime, buildInfo } = globalObject
  if (!viteConfigRuntime) {
    if (buildInfo) {
      viteConfigRuntime = buildInfo.viteConfigRuntime
    } else {
      assert(!isProcessSharedWithVite()) // process shared with Vite => globalObject.viteConfigRuntime should be set
      assert(!isProd()) // production => globalObject.buildInfo.viteConfigRuntime should be set
      assert(isNonRunnableDev())
      const rpc = getViteRPC<ViteRPC>()
      viteConfigRuntime = await rpc.getViteConfigRuntimeRPC()
    }
  }
  assert(viteConfigRuntime)

  return {
    viteConfigRuntime,
    ...resolveBaseRuntime(viteConfigRuntime, globalContext.config),
  }
}

function clearGlobalContext() {
  debug('clearGlobalContext()')
  objectReplace(globalObject, getInitialGlobalObject(), ['prodBuildEntryPrevious'])
}

function getInitialGlobalObject() {
  debug('getInitialGlobalObject()')
  const { promise: viteDevServerPromise, resolve: viteDevServerPromiseResolve } = genPromise<ViteDevServer>()
  return {
    viteDevServerPromise,
    viteDevServerPromiseResolve,
  }
}

function resolveBaseRuntime(viteConfigRuntime: BuildInfo['viteConfigRuntime'], config: GlobalConfigPublic['config']) {
  const baseViteOriginal = viteConfigRuntime._baseViteOriginal
  const baseServerUnresolved = config.baseServer ?? null
  const baseAssetsUnresolved = config.baseAssets ?? null
  return resolveBase(baseViteOriginal, baseServerUnresolved, baseAssetsUnresolved)
}

function isProcessSharedWithVite(): boolean {
  const yes = globalThis.__VIKE__IS_PROCESS_SHARED_WITH_VITE ?? false
  if (yes) assert(!isNonRunnableDev())
  return yes
}

function isRunnable(viteDevServer: ViteDevServer): boolean {
  const yes =
    // Vite 5
    !viteDevServer.environments ||
    // Vite 6 or above
    isRunnableDevEnvironment(viteDevServer.environments.ssr)
  if (yes) assert(!isNonRunnableDev())
  return yes
}

function isProd(): boolean {
  const isProduction = isProdOptional()
  if (isProduction === null) {
    if (globalObject.isAfterFirstRenderPageCall) {
      // When using a production server without vike-server, there isn't any reliable signal we can use to determine early whether the environment is production or development. If renderPage() was called then some non-negligible amount of time passed — it's likely that, in dev, one of the Vite hooks should have already sent a signal we can use to determine prod/dev.
      return true
    } else {
      assert(false)
    }
  }
  return isProduction
}
function isProdOptional(): boolean | null {
  const vikeApiOperation = getVikeApiOperation()?.operation ?? null

  // setGlobalContext_prodBuildEntry() was called
  const yes1 = !!globalObject.prodBuildEntry
  const yes2 = globalObject.isPrerendering === true
  // Vike CLI & Vike API
  const yes3 = !!vikeApiOperation && vikeApiOperation !== 'dev'
  // Vite command
  const yes4 = globalObject.isProductionAccordingToVite === true
  // getGlobalContextAsync(isProduction)
  const yes5 = globalObject.isProductionAccordingToUser === true
  // vite-plugin-vercel
  const yes6 = globalObject.isProductionAccordingToPhotonVercel === true
  const yes7 = globalThis.__VIKE__IS_DEV === false
  const yes: boolean = yes1 || yes2 || yes3 || yes4 || yes5 || yes6 || yes7

  const no1 = !!globalObject.viteDevServer
  // Vike CLI & Vike API
  const no2 = vikeApiOperation === 'dev'
  // Vite command
  const no3 = globalObject.isProductionAccordingToVite === false
  // getGlobalContextAsync(isProduction)
  const no4 = globalObject.isProductionAccordingToUser === false
  // @cloudflare/vite-plugin
  const no5 = isNonRunnableDev()
  const no6 = globalThis.__VIKE__IS_DEV === true
  const no: boolean = no1 || no2 || no3 || no4 || no5 || no6

  const debug = { yes1, yes2, yes3, yes4, yes5, yes6, yes7, no1, no2, no3, no4, no5, no6 }
  assert(typeof yes === 'boolean', debug)
  assert(typeof no === 'boolean', debug)

  if (yes) {
    assert(no === false, debug)
    return true
  }
  if (no) {
    assert(yes === false, debug)
    return false
  }
  return null
}
